package easycloud.ecs.provider.local;

import java.io.File;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import easycloud.ecs.common.domain.Folder;
import easycloud.ecs.common.domain.Item;
import easycloud.ecs.common.domain.Log.LOG_TYPE;
import easycloud.ecs.common.domain.Provider;
import easycloud.ecs.common.domain.Sync;
import easycloud.ecs.provider.api.IProvider;
import easycloud.ecs.provider.api.ProviderException;
import easycloud.ecs.provider.model.IStorageObject;
import easycloud.ecs.provider.model.IStorageObjectMetadata;
import easycloud.ecs.provider.model.local.LocalStorageObject;
import easycloud.ecs.provider.model.local.LocalStorageObjectMetadata;
import easycloud.ecs.provider.service.SendLogService;

public class LocalProvider implements IProvider {

	private SendLogService sendLogService;

	private Provider config;

	private boolean isReady = false;

	private static final Log logger = LogFactory.getLog(LocalProvider.class);

	private Map<String, String> syncPrefix = new HashMap<String, String>();

	public LocalProvider(Provider config) {
		this.config = config;
		for (Sync sync : this.config.getSyncs()) {
			syncPrefix.put(sync.getFolder().getName(), sync.getPath());
		}
	}

	@Override
	public boolean delete(Item item) throws ProviderException {
		if (!ready()) {
			throw new ProviderException(getName() + " Provider is not ready");
		}

		boolean result = false;
		try {
			String filePath = getPhysicalPath(item);
			FileUtils.deleteQuietly(new File(filePath));
			result = true;
			LocalProvider.logger.info(getName() + " delete OK: "
					+ item.getPath());
		} catch (Exception e) {
			LocalProvider.logger.warn(getName() + " delete failed", e);
			throw new ProviderException(getName() + " delete failed", e,
					item.getPath());
		} finally {
			easycloud.ecs.common.domain.Log log = new easycloud.ecs.common.domain.Log();
			log.setLogType(LOG_TYPE.DELETE);
			log.setCreateTime(new Date());
			log.setResult(result);
			log.setProvider(config);
			sendLogService.addLog(log);
		}
		return result;
	}

	@Override
	public IStorageObject getItem(Item item) throws ProviderException {
		if (!ready()) {
			throw new ProviderException(getName() + " Provider is not ready");
		}

		boolean result = false;
		File object = null;
		try {
			String filePath = getPhysicalPath(item);
			File file = new File(filePath);
			if (file.exists()) {
				object = file;
			} else {
				throw new Exception("No Such File Found");
			}

			LocalProvider.logger.info(getName() + " getFile OK: "
					+ item.getPath());
			result = true;
		} catch (Exception e) {
			LocalProvider.logger.warn(getName() + " getFile failed", e);
			throw new ProviderException(getName() + " getFile failed", e,
					item.getPath());
		} finally {
			easycloud.ecs.common.domain.Log log = new easycloud.ecs.common.domain.Log();
			log.setLogType(LOG_TYPE.GET);
			if (object != null) {
				log.setLength(object.length());
			}
			log.setCreateTime(new Date());
			log.setResult(result);
			log.setProvider(config);
			sendLogService.addLog(log);
		}
		return new LocalStorageObject(object, item);
	}

	@Override
	public String getName() {
		return config.getName();
	}

	private String getPhysicalPath(Item item) throws ProviderException {
		String itemPath = item.getPath();
		String folderName = itemPath.substring(0, itemPath.indexOf("/"));
		String prefix = syncPrefix.get(folderName);
		if (prefix != null) {
			return itemPath.replaceFirst(folderName, prefix);
		} else {
			throw new ProviderException("No Sync Path Found");
		}
	}

	private String getLogicalPath(File file, Folder folder)
			throws ProviderException {
		String prefix = getPhysicalPath(folder);
		return StringUtils.replaceOnce(file.getAbsolutePath(), prefix,
				folder.getName());
	}

	private String getPhysicalPath(Folder folder) throws ProviderException {
		return syncPrefix.get(folder.getName());
	}

	@Override
	public IStorageObjectMetadata head(Item item) throws ProviderException {
		if (!ready()) {
			throw new ProviderException(getName() + " Provider is not ready");
		}

		File result = null;
		try {
			String filePath = getPhysicalPath(item);
			File file = new File(filePath);
			if (file.exists()) {
				result = file;
			} else {
				throw new Exception("No Such File Found");
			}
			LocalProvider.logger
					.info(getName() + " head OK: " + item.getPath());
		} catch (Exception e) {
			LocalProvider.logger.warn(getName() + " head failed", e);
			throw new ProviderException(getName() + " head failed", e,
					item.getPath());
		} finally {
			easycloud.ecs.common.domain.Log log = new easycloud.ecs.common.domain.Log();
			log.setLogType(LOG_TYPE.HEAD);
			log.setCreateTime(new Date());
			log.setProvider(config);
			sendLogService.addLog(log);
		}
		return new LocalStorageObjectMetadata(result, item);
	}

	@Override
	public List<IStorageObjectMetadata> list(Folder folder)
			throws ProviderException {
		if (!ready()) {
			throw new ProviderException(getName() + " Provider is not ready");
		}

		Collection<File> listing = null;
		try {
			String filePath = getPhysicalPath(folder);
			File file = new File(filePath);
			if (file.exists()) {
				listing = FileUtils.listFiles(file, null, true);
			} else {
				throw new Exception("No Such File Found");
			}
			LocalProvider.logger.info(getName() + " list OK: "
					+ folder.getName());
		} catch (Exception e) {
			LocalProvider.logger.warn(getName() + " list failed", e);
			throw new ProviderException(getName() + " list failed", e,
					folder.getName());
		} finally {
			easycloud.ecs.common.domain.Log log = new easycloud.ecs.common.domain.Log();
			log.setLogType(LOG_TYPE.LIST);
			log.setCreateTime(new Date());
			log.setProvider(config);
			sendLogService.addLog(log);
		}
		if (listing != null) {
			List<IStorageObjectMetadata> result = new ArrayList<IStorageObjectMetadata>();
			for (File file : listing) {
				Item item = new Item();
				String path = getLogicalPath(file, folder);
				item.setPath(path);
				item.setFolder(folder);
				result.add(new LocalStorageObjectMetadata(file, item));
			}
			return result;
		} else {
			return null;
		}
	}

	@Override
	public IStorageObjectMetadata putItem(Item item, InputStream is, long length)
			throws ProviderException {
		if (!ready()) {
			throw new ProviderException(getName() + " Provider is not ready");
		}

		boolean result = false;
		File file = null;
		try {
			String filePath = getPhysicalPath(item);
			file = new File(filePath);
			FileUtils.copyInputStreamToFile(is, file);
			LocalProvider.logger.info(getName() + " putFile OK: "
					+ item.getPath());
			result = true;
		} catch (Exception e) {
			LocalProvider.logger.warn(getName() + " putFile failed", e);
			throw new ProviderException(getName() + " putFile failed", e,
					item.getPath());
		} finally {
			easycloud.ecs.common.domain.Log log = new easycloud.ecs.common.domain.Log();
			log.setLogType(LOG_TYPE.PUT);
			log.setLength(length);
			log.setCreateTime(new Date());
			log.setResult(result);
			log.setProvider(config);
			sendLogService.addLog(log);
		}
		return new LocalStorageObjectMetadata(file, item);
	}

	@Override
	public boolean ready() {
		if (isReady) {
			return true;
		}

		for (String syncDir : syncPrefix.values()) {
			File dir = new File(syncDir);
			if (!dir.exists()) {
				dir.mkdirs();
			}
		}

		isReady = true;
		return isReady;
	}

	@Override
	public void setSendLogService(SendLogService sendLogService) {
		this.sendLogService = sendLogService;
	}
}
